import Decimal from "./decimal.js"
import Rational from "./rational.js"
import Int from "./int.js"

class Fraction extends Rational {
    /**
     * @type {{ numerator: Rational, denominator: Rational }}
     */
    #value = {
        numerator: new Int(0n),
        denominator: new Int(1n),
        sign: 0
    }

    /**
     * @type {Rational|null}
     */
    #reciprocal = null

    get [Symbol.toStringTag]() {
        return 'Fraction '+this.toString()
    }

    toString() {
        return (sign?'-':'')+'('+this.#value.numerator.toString()+')/('+this.#value.denominator.toString()+')'
    }

    /**
     * 
     * @param {Rational} numerator 
     * @param {Rational} denominator 
     * @param {0|1} [sign=0] - 符号 0正，1负
     */
    constructor(numerator, denominator, sign=0) {
        super("fraction")
        if (denominator instanceof Int && denominator.value === 0n) 
            throw new Error("Denominator can't be 0")
        if ((!(denominator instanceof Rational)) || (!(numerator instanceof Rational)))
            throw new Error("Numerator and denominator must be Rational")

        this.#value = {
            numerator, denominator, sign
        }
    }

    /**
     * @param {Rational} value 
     */
    add(value) {
        if (value instanceof Int) {
            return new Fraction(this.#value.numerator.add(this.#value.denominator.mul(value)), this.#value.denominator)
        }
        if (value instanceof Fraction) {
            return new Fraction(this.#value.numerator.mul(value.value.denominator).add(this.#value.denominator.
                mul(value.value.numerator)), this.#value.denominator.mul(value.value.denominator)).simplify()
        }
        if (value instanceof Decimal) {
            return value.toFraction().add(this)
        }
    }

    /**
     * 
     * @param {Rational} value 
     */
    sub(value) {
        if (!Rational.is(value)) throw new Error("Value must be Rational")
        return this.add(value.mul(new Int(-1n)))
    }

    /**
     * 
     * @param {Rational|Int} value 
     * @returns Rational
     */
    mul(value) {
        if (!Rational.is(value))
            throw new Error("Value must be Rational")
        /**
         * @type {Fraction|undefined}
         */
        let result = undefined
        if (value instanceof Int) {
            result = new Fraction(this.#value.numerator.mul(value), this.#value.denominator)
        }
        if (value instanceof Fraction) {
            result = new Fraction(this.#value.numerator.mul(value.value.numerator), this.#value.denominator.mul(value.value.denominator))
        }
        if (value instanceof Decimal) 
            result = value.toFraction().mul(this)
        return result.simplify()
    }

    div(value) {
        if (!Rational.is(value))
            throw new Error("Value must be Rational")
        return this.mul(value.reciprocal).simplify()
    }

    /**
     * 
     * @returns {Int|Fraction}
     */
    simplify() {
        if (this.#value.numerator.value === 0n) 
            return new Int(0n)
        if (this.#value.numerator.value === this.#value.denominator.value)
            return new Int(1n)
        /**
         * 
         * @param {{ numerator: Int, denominator: Int }} value
         */
        function run(value) {
            const primeFractors = {
                numerator: value.numerator.primeFractors,
                denominator: value.denominator.primeFractors
            }

            const freqMap = primeFractors.denominator.reduce((map, num) => {
                map[num] = (map[num] || 0) + 1;
                return map;
            }, {});
            
            const common = primeFractors.numerator.reduce((result, num) => {
                if (freqMap[num] > 0) {
                    result.push(num);
                    freqMap[num]--; 
                }
                return result;
            }, []);

            let i = 0;
            while (i < common.length) {
                let j1 = false;
                primeFractors.denominator = primeFractors.denominator.filter(v => {
                    if (v.value == common[i].value) {
                        if (j1) return true;
                        else {
                            j1 = true;
                            return false
                        }
                    }
                    return true
                })
                j1 = false;
                primeFractors.numerator = primeFractors.numerator.filter(v => {
                    if (v.value == common[i].value) {
                        if (j1) return true;
                        else {
                            j1 = true;
                            return false
                        }
                    }
                    return true
                })
                i++;
            }
            const result = {
                numerator: primeFractors.numerator.reduce((last, current) => last.mul(current), new Int(1n)),
                denominator: primeFractors.denominator.reduce((last, current) => last.mul(current), new Int(1n))
            }
            return result;
        }
        /**
         * @type {{ numerator: Int, denominator: Int }|undefined}
         */
        let result = undefined
        if (this.#value.numerator instanceof Int && this.#value.denominator instanceof Int) {
            result = run(this.#value)
        }
        else if((!(this.#value.numerator instanceof Fraction)) && (!(this.#value.denominator instanceof Fraction))) {
            /**
             * @type {Rational}
             */
            let newNumerator = new Int(0)
            if (this.#value.numerator instanceof Int) {
                newNumerator = this.#value.numerator
            } else {
                newNumerator = this.#value.numerator.toFraction()
            }
            /**
             * @type {Int|Fraction|Int}
             */
            const mulValue = newNumerator.mul(this.#value.denominator.reciprocal)
            if (mulValue instanceof Int) 
                return mulValue
            else
                result = run(mulValue.value)
        }
        if(result.denominator.value === 1n)
            return new Int(result.numerator)
        if (result.numerator.value === 0n)
            return new Int(0)
        return new Fraction(result.numerator, result.denominator)
    }

    toFraction() {
        return this
    }

    /**
     * @returns {{ numerator: Rational, denominator: Rational }}
     */
    get value() {
        return this.#value
    }

    /**
     * @returns {Rational}
     */
    get reciprocal() {
        if (!this.#reciprocal)
            this.#reciprocal = new Fraction(this.#value.denominator, this.#value.numerator).simplify()
        return this.#reciprocal
    }
}
export default Fraction